import scipy
import copy
import random
from helper_codes.general_stimulus import general_stimulus, GeneralStim

def moving_leading_edge_settings():
    '''
    This function tells you what settings are needed and in what order for this stimulus.
    Inputs:
        None
    Returns:
        settings        : A list of tupples.  In each tupple is
                            (setting_name, setting_description, default_value).
                          The list is of length num_settings + 1, so that the final
                             value in the list is True/False wether or not the stimulus
                             can be triggered via the parallel port or not.
    '''
    settings = []
    settings.append( ('rim_size',
                     'The DIAMETER of the rim on which edges begin and end movement. (normalized RF units)',
                     1.0 ))
    settings.append( ('edge_width',
                     'The width of the short side of the bar. (normalized RF units)',
                     0.2 ))
    settings.append( ('duration',
                     'The amount of time the stimulus will move. (seconds)',
                     5.0 ))
    settings.append( ('wait_time',
                     'The amount of time after stimulus movement. (seconds)',
                     1.0 ))
    settings.append( ('angles',
                     'The angles describing where the bar will start moving from. (normalized RF units) [list]',
                     [0, 45, 90, 135, 180, 225, 270, 315] ))
    settings.append( ('shade',
                     '1.0 for black dots, -1.0 for white dots.',
                     1.0 ))
    settings.append( ('random_seed',
                     'The seed for the random number generator',
                     40 ))
    settings.append(True) # this stimulus can be triggered
    return settings


def moving_leading_edge_runtime(rim_size,  edge_width, duration, wait_time, angles,
                              shade, random_seed): 
    """
    Calculate and return the number of seconds that this stimulus will need to run, after being triggered.
    """
    return (duration + wait_time)*len(angles) + 0.99 # 0.99 added so rounding is always up.


class Stim(GeneralStim):
    """
    This is a basic stimulus object, it must have:
        .setup(window) method to set itself up to be drawn
        .draw() method to draw itself to the display
        .evolve() method to set itself up for the next draw
        .exhausted variable set to True after stim is done.
    """
    def __init__( self, rim_size, edge_width, duration, wait_time, angles, shade,
                  random_seed, refresh_rate, preview, save_frames):
        self.rim_size = rim_size
        self.edge_width = edge_width
        self.duration = duration
        self.wait_time = wait_time
        self.angles = angles
        self.shade = shade
        self.refresh_rate = refresh_rate
        self.preview = preview
        self.save_frames = save_frames

        random.seed(random_seed)
        self.exhausted = False
        self.times_evolved = 0

    def setup(self, window):
        from psychopy import visual

        def find_positions(rim_size, angle, num_frames, bar_length):
            """
            Returns the positions of point moving from one side of a rim to another
                side of the rim.
            """
            import scipy
            # start with the bar moving along the x axis, then rotate as appropriate
            unr_x = scipy.linspace(-bar_length/2.0-rim_size/2.0, 
                                   -bar_length/2.0+rim_size/2.0, num_frames)
            unr_y = [0.0 for i in xrange(num_frames)]

            # rotate the coordinates
            angle_rad = -angle*scipy.pi/180.0
            rot_matrix = [[scipy.cos(angle_rad), scipy.cos(angle_rad+scipy.pi/2.0)],
                          [scipy.cos(scipy.pi/2.0-angle_rad), scipy.cos(angle_rad)]]
            rot_matrix = scipy.array(rot_matrix)
            
            positions = []
            for ux, uy in zip(unr_x,unr_y):
                positions.append(scipy.dot( scipy.array([ux,uy]), rot_matrix) )

            return positions

        bar_length = 1e4
        # determine the number of frames per angle.
        num_frames   = int(self.duration*self.refresh_rate)
        wait_frames  = int(self.wait_time*self.refresh_rate)
        self.num_frames = num_frames
        self.wait_frames = wait_frames

        # determine the angles list order randomly.
        passed_angles = copy.deepcopy(self.angles)
        angles = []
        for i in xrange(len(passed_angles)):
            index = random.randint(0,len(passed_angles)-1)
            angles.append(copy.deepcopy(passed_angles[index]))
            del(passed_angles[index])
        print "angles used: %s" % angles

        # calculate the positions vector based on the rim_size and such
        positions    = []
        orientations = []
        for angle in angles:
            positions.extend( find_positions(self.rim_size, angle, 
                                             num_frames,bar_length) )
            orientations.extend([angle for i in xrange(num_frames)])
            # add in enough frames to account for the wait after each movement.
            last_pos = copy.deepcopy(positions[-1])
            last_ori = copy.deepcopy(orientations[-1])
            positions.extend( [last_pos for i in xrange(wait_frames)] )
            orientations.extend([last_ori for i in xrange(wait_frames)])

        self.positions = scipy.array(positions)
        self.orientations = scipy.array(orientations)

        # construct the patch_info for the bar
        texture = scipy.ones((4,4),dtype=scipy.float64)*-1.0*self.shade
        self.bar = visual.PatchStim( window, 
                                        tex=texture, 
                                        pos=self.positions[0],
                                        size=(bar_length, self.edge_width), 
                                        units='norm')
        self.textures = [self.bar]


    def draw(self):
        self.bar.draw()

    def evolve(self):
        self.times_evolved += 1

        bar_i = min(len(self.positions)-1, self.times_evolved)
        self.bar.setPos(self.positions[bar_i])
        self.bar.setOri(-self.orientations[bar_i])

        
        if self.times_evolved >= len(self.positions):
            if (not self.preview) or self.save_frames:
                self.exhausted = True
            else:
                self.times_evolved = 0 # continue looping forever in preview mode.
    

def moving_leading_edge( rim_size, edge_width, duration, wait_time, angles, shade,
                         random_seed, 
                         win_size=[1440,900], 
                         refresh_rate=60,
                         window=None,
                         eye_cup=None, 
                         rf=None, 
                         save_frames=False,
                         save_frames_factor=1.0,
                         save_frames_size=100,
                         display_saved_frames=False,
                         preview=True):
    """
    This stimulus shows a leading edge of specified width moving radially through the
        receptive field.
    Inputs:
        rim_size        : the radius of the rim where bars begin and end in normalized units.
        edge_width      : the width of the moving leading edge
        duration        : the time where the bar will be moving
        wait_time       : the time after movement, before screen returns to previous state.
        angles          : a list of angles in degrees through which the bar will move in sequence
        --- kwargs --
        window          : a psychopy.visual Window object, if not passed it will be constructed.
                        :    if passed it will not be closed (a blank screen will be drawn)
        preview         : If false the program will wait for a pulse trigger, otherwise it won't.
        save_frames_factor  : if None, then no frames are saved, otherwise it is a 
                                scaling factor so we can scale down the frames and
                                output a frames.gif (an animated gif file).
        eye_cup         : an Eye_cup object, see its docstring for more info.
        rf              : RF object, to set up new coordinate system.
    Returns:
        times     : The times when frames were shown, with zero being the first frame time.
    """
    stim_list = [ Stim( rim_size, edge_width, duration, wait_time, angles, shade,
                        random_seed, refresh_rate, preview, save_frames) ]
    return_values = general_stimulus( stim_list, shade, win_size, 
                                         window=window,
                                         eye_cup=eye_cup, 
                                         rf=rf, 
                                         save_frames=save_frames,
                                         save_frames_factor=save_frames_factor,
                                         save_frames_size=save_frames_size,
                                         display_saved_frames=display_saved_frames,
                                         preview=preview)
    return return_values


if __name__ == '__main__':
    # ---------------------------------
    # -- Set the stimulus parameters --
    # ---------------------------------
    rim_size    = 1.0                  # diameter of the rim where bars start
    edge_width  = 0.1               # how wide the bar is
    duration    = 1.0                # in seconds
    shade       = 1                            # 1.0 for white, -1.0 for black background
    wait_time   = 1.0                # in seconds
    angles      = [0, 45, 90, 135, 180, 225, 270, 315] # the angles the bar will move through
    random_seed = 40

    return_values = moving_leading_edge(rim_size,  edge_width, duration, wait_time, 
                      angles, shade, random_seed,
                        save_frames = True,
                        display_saved_frames = True)


